package com.bookbuf.api.responses.parsers.impl;

import com.bookbuf.api.exceptions.PuDongCode;
import com.bookbuf.api.exceptions.PuDongException;
import com.bookbuf.api.responses.Response;
import com.bookbuf.api.responses.parsers.annotations.IgnoreKey;
import com.bookbuf.api.responses.parsers.annotations.Implementation;
import com.bookbuf.api.responses.parsers.annotations.Key;
import com.ipudong.util.basic.ApiLog;
import com.ipudong.util.parser.StringUtil;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_DEFAULT;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_DOUBLE;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_IMPLEMENTATION;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_INTEGER;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_JSON;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_LIST;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_LONG;
import static com.bookbuf.api.exceptions.PuDongCode.PARSER_ERROR_STRING;

/**
 * author: robert.
 * date :  2016/12/13.
 */

public abstract class PuDongParserImpl implements Serializable, Response {

    public transient ApiLog logger = new ApiLog(getClass().getSimpleName());

    public PuDongParserImpl(JSONObject jsonObject) {
        init(jsonObject);
    }

    public PuDongParserImpl(JSONArray array, String tag) {
        parseJSONArray(array);
    }

    private void init(JSONObject wrapperJSONObject) {
        Object object = getRealityJSONObject(wrapperJSONObject);
        if (object == null) return;
        if (equalsOriginDataType(object.getClass())) {
            parseOriginDataType(object);
        } else if (object instanceof JSONObject) {
            parseJSONObject(object);
        } else if (object instanceof JSONArray) {
            parseJSONArray(object);
        } else {
            throw new IllegalArgumentException("不支持的数据类型:" + object.getClass());
        }
    }

    private boolean equalsBoolean(Class<?> clazz) {
        return clazz.equals(boolean.class) || clazz.equals(Boolean.class);
    }

    private boolean equalsInteger(Class<?> clazz) {
        return clazz.equals(Integer.class) || clazz.equals(int.class);
    }

    private boolean equalsLong(Class<?> clazz) {
        return clazz.equals(Long.class) || clazz.equals(long.class);
    }

    private boolean equalsString(Class<?> clazz) {
        return clazz.equals(String.class);
    }

    private boolean equalsDouble(Class<?> clazz) {
        return clazz.equals(Double.class) || clazz.equals(double.class);
    }

    private boolean equalsList(Class<?> clazz) {
        return clazz.equals(List.class);
    }

    private boolean equalsOriginDataType(Class<?> clazz) {
        return equalsDouble(clazz) || equalsLong(clazz) || equalsString(clazz) || equalsInteger(clazz) || equalsBoolean(clazz);
    }

    private boolean equalsInvalidJSONObject(JSONObject jsonObject) {
        return jsonObject.toString().equalsIgnoreCase("{}");
    }

    private String getFieldName(Field field) {
        if (field.isAnnotationPresent(Key.class)) {
            Key name = field.getAnnotation(Key.class);
            return name.value();
        } else {
            return field.getName();
        }
    }

    private Class<?> getImplementationClass(Field field) {
        try {
            if (field.isAnnotationPresent(Implementation.class)) {
                Implementation name = field.getAnnotation(Implementation.class);
                return name.value();
            } else {
                logger.d("getImplementationClass: fieldName = " + field.getName());
                // TODO: 2017/1/18 如果是泛型，则会取泛型的形参。
                ParameterizedType type = (ParameterizedType) field.getGenericType();
                return (Class<?>) type.getActualTypeArguments()[0];
                // TODO: 2017/1/18 但如果不是泛型，就应该取其真实的参数。
            }
        } catch (Exception e) {
            throw new PuDongException(0x9999);
        }
    }

    /**
     * 外部传入的JSON 可能包装了Root层，也可能未包装。原因是初次解析时会有Root层，但是嵌套的解析层都不需要解析Root。
     *
     * @param wrapperJSONObject 外部传入的json
     * @return 用于真实处理的json
     */
    protected Object getRealityJSONObject(JSONObject wrapperJSONObject) {
        if (wrapperJSONObject == null) {
            return null;
        }
        Object reality;
        if (wrapperJSONObject.has("root")) {
            try {
                reality = wrapperJSONObject.get("root");
            } catch (JSONException e) {
                reality = null;
            }
        } else {
            reality = wrapperJSONObject;
        }
        return reality;
    }


    private void parseOriginDataType(Object object) {
        logger.d(String.format("解析原始类型:%s，类型:%s", object, object.getClass()));
        final Field field = getClass().getDeclaredFields()[0];
        final Class clazzType = field.getType();
        setField(field, clazzType, object);
    }

    private void parseJSONObject(Object object) {
        JSONObject jsonObject = (JSONObject) object;
        if (equalsInvalidJSONObject(jsonObject)) {
            logger.w("无效的JSON数据，忽略后继的解析" + jsonObject.toString());
            return;
        }
        Field[] fields = getClass().getDeclaredFields();

        for (Field field : fields) {

            final String fieldName = getFieldName(field);
            final Class clazzType = field.getType();

            logger.d("fieldName = " + fieldName + ",clazzType = " + clazzType);
            if (!field.isAnnotationPresent(IgnoreKey.class)) {

                Object fieldValue;
                try {
                    fieldValue = jsonObject.get(fieldName);
                    logger.d(String.format("开始解析键值:%s，类型:%s，值:%s", fieldName, clazzType, fieldValue));
                } catch (JSONException e) {
                    logger.w(String.format("解析异常:{%s}", e.getMessage()));
                    fieldValue = null;
                }
                if (fieldValue != null) {
                    setField(field, clazzType, fieldValue);
                } else {
                    setDefault(field, clazzType);
                }
            } else {
                logger.d(String.format("忽略:%s，类型:%s", fieldName, clazzType));
            }
        }

    }

    private void parseJSONArray(Object object) {

        Field[] fields = getClass().getDeclaredFields();

        Field field = null;
        for (Field f : fields) {
            Class<?> clazz = f.getType();
            if (clazz.getCanonicalName().equalsIgnoreCase("java.util.List")) {
                field = f;
                break;
            } else {
                logger.d("传入数据为数组，解析数据类型：" + clazz.getCanonicalName());
            }
        }
        if (field == null) {
            throw new IllegalArgumentException("传入数据为数组，但并未指定解析数组的对象（java.util.List）。");
        }

        final JSONArray array = (JSONArray) object;
        setResponseList(field, array);

    }

    private void checkAccessible(Field field) {
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
    }

    private void setField(Field field, Class clazzType, Object fieldValue) {

        checkAccessible(field);

        if (fieldValue == JSONObject.NULL) {
            setDefault(field, clazzType);
        } else if(fieldValue.equals("")){
            setDefault(field, clazzType);
        }else if (equalsBoolean(clazzType)) {
            setBoolean(field, fieldValue);
        } else if (equalsInteger(clazzType)) {
            setInteger(field, fieldValue);
        } else if (equalsString(clazzType)) {
            setString(field, fieldValue);
        } else if (equalsLong(clazzType)) {
            setLong(field, fieldValue);
        } else if (equalsDouble(clazzType)) {
            setDouble(field, fieldValue);
        } else if (equalsList(clazzType)) {
            setResponseList(field, fieldValue);
        } else {
            setResponse(field, fieldValue);
        }
    }

    private void setDouble(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        try {
            if (fieldValue instanceof String)
                field.set(this, Double.parseDouble((String) fieldValue));
            else
                field.set(this, fieldValue);
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PARSER_ERROR_DOUBLE);
        } catch (NumberFormatException e) {
            throw new PuDongException(e, field, PARSER_ERROR_DOUBLE);
        }
    }

    private void setLong(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        try {
            if (fieldValue instanceof String) {
                if (StringUtil.isEmpty((String) fieldValue)) {
                    field.set(this, -1L);
                } else {
                    field.set(this, Long.parseLong((String) fieldValue));
                }
            } else
                field.set(this, fieldValue);
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PARSER_ERROR_LONG);
        } catch (NumberFormatException e) {
            throw new PuDongException(e, field, PARSER_ERROR_LONG);
        }
    }

    private void setString(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        try {
            if (fieldValue instanceof JSONObject || fieldValue instanceof JSONArray)
                field.set(this, fieldValue.toString());
            else
                field.set(this, String.valueOf(fieldValue));
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PARSER_ERROR_STRING);
        }
    }

    private void setInteger(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        try {
            if (fieldValue instanceof String)
                if (StringUtil.isEmpty((String) fieldValue)) {
                    field.set(this, -1);
                } else {
                    field.set(this, Integer.parseInt((String) fieldValue));
                }
            else
                field.set(this, fieldValue);
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PARSER_ERROR_INTEGER);
        } catch (NumberFormatException e) {
            throw new PuDongException(e, field, PARSER_ERROR_INTEGER);
        }
    }

    private boolean checkIgnoreKey(Field field) {
        return field.isAnnotationPresent(IgnoreKey.class);
    }

    private void setResponse(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        if (checkIgnoreKey(field)) {
            return;
        }
        Class<?> actualClass = getImplementationClass(field);
        try {
            Constructor constructor = actualClass.getDeclaredConstructor(JSONObject.class);
            field.set(this, constructor.newInstance(fieldValue));
        } catch (Exception e) {
            throw new PuDongException(e, field, PARSER_ERROR_IMPLEMENTATION);
        }
    }

    private void setResponseList(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);
        if(fieldValue.equals("")){
            return;
        }
        List list = new ArrayList();
        JSONArray array = (JSONArray) fieldValue;
        if (array != null && array.length() > 0) {

            try {
                for (int i = 0; i < array.length(); i++) {
                    Object object = array.get(i);
                    if (equalsOriginDataType(object.getClass())) {
                        list.add(object);
                    } else {
                        Class<?> actualClass = getImplementationClass(field);
                        Constructor constructor = actualClass.getDeclaredConstructor(org.json.JSONObject.class);
                        list.add(constructor.newInstance(object));
                    }
                }

            } catch (JSONException e) {
                throw new PuDongException(e, field, PARSER_ERROR_JSON);
            } catch (InstantiationException e) {
                throw new PuDongException(e, field, PARSER_ERROR_IMPLEMENTATION);
            } catch (InvocationTargetException e) {
                throw new PuDongException(e, field, PARSER_ERROR_IMPLEMENTATION);
            } catch (NoSuchMethodException e) {
                throw new PuDongException(e, field, PARSER_ERROR_IMPLEMENTATION);
            } catch (IllegalAccessException e) {
                throw new PuDongException(e, field, PARSER_ERROR_IMPLEMENTATION);
            }
        }
        try {
            field.set(this, list);
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PARSER_ERROR_LIST);
        }
    }

    private void setBoolean(Field field, Object fieldValue) throws PuDongException {
        checkAccessible(field);

        try {
            if (fieldValue instanceof Integer) {
                int value = (int) fieldValue;
                // 兼容当返回的数据是id，但是客户端只需要判断成功失败状态即可。
                if (value >= 1) {
                    field.set(this, true);
                } else if (value == 0) {
                    field.set(this, false);
                }
            } else if (fieldValue instanceof Long) {
                long value = (long) fieldValue;
                if (value == 1) {
                    field.set(this, true);
                } else if (value == 0) {
                    field.set(this, false);
                } else {
                    throw new PuDongException(null, field, PuDongCode.PARSER_ERROR_BOOLEAN);
                }
            } else if (fieldValue instanceof String) {

                // false or true
                String value = (String) fieldValue;

                if (value.equalsIgnoreCase("true")
                        || value.equalsIgnoreCase("false")
                        || value.equalsIgnoreCase("Y")
                        || value.equalsIgnoreCase("N")
                        || value.equalsIgnoreCase("0")
                        || value.equalsIgnoreCase("1")) {

                    if (value.equalsIgnoreCase("true")) {
                        field.set(this, true);
                    } else if (value.equalsIgnoreCase("false")) {
                        field.set(this, false);
                    }
                    // N or Y
                    if (value.equalsIgnoreCase("Y")) {
                        field.set(this, true);
                    } else if (value.equalsIgnoreCase("N")) {
                        field.set(this, false);
                    }
                    // 0 or 1
                    if (value.equalsIgnoreCase("1")) {
                        field.set(this, true);
                    } else if (value.equalsIgnoreCase("0")) {
                        field.set(this, false);
                    }
                } else {
                    throw new PuDongException(null, field, PuDongCode.PARSER_ERROR_BOOLEAN);
                }
            } else {
                field.set(this, fieldValue);
            }
        } catch (IllegalAccessException e) {
            throw new PuDongException(e, field, PuDongCode.PARSER_ERROR_BOOLEAN);
        }

    }

    private void setDefault(Field field, Class<?> clazz) throws PuDongException {
        checkAccessible(field);
        try {
            if (equalsBoolean(clazz)) {
                field.set(this, false);
            } else if (equalsInteger(clazz)) {
                field.set(this, -1);
            } else if (equalsString(clazz)) {
                field.set(this, null);
            } else if (equalsLong(clazz)) {
                field.set(this, -1L);
            } else if (equalsDouble(clazz)) {
                field.set(this, -1);
            } else {
                field.set(this, null);
            }
        } catch (IllegalAccessException | IllegalArgumentException e1) {
            throw new PuDongException(e1, field, PARSER_ERROR_DEFAULT);
        }
    }
}
